Repository navigation
feat(web): Solenta Web switch, per-device tokens with revoke, QR pairing, Tailscale HTTPS (#1512 I2, #245) - #1515
Conversation
Named device tokens in web-access.json (0600), shown once, sha256 on disk, constant-time authorize over every row. The single web-token is adopted as Legacy device. Adds qrcode-generator for the pairing QR. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#1512 I2) webBridge/webServer take an authorize(token) function; each authed socket remembers its device so revoke terminates it at once. Failed auth is rate-limited per address (10/min) across the socket and artifact routes. webAccess.js owns start/stop (loopback unless network access is on), listen URLs, and a detect-only Tailscale Serve helper. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…I2) main.js owns one webAccess: --serve-web still prints its token (now the Legacy device row); otherwise the saved Settings switch restores the server on loopback, or 0.0.0.0 only if network access was turned on. web:* handlers are desktop only. Declared in ipcChannels, CoderApi, preload, devCoder and fakeCoder. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…scale (#1512 I2) Connections pane: serve switch, network-access switch with plain-HTTP warning, Add device with a one-time link and a locally rendered QR, device list with last seen and Revoke, and Expose over Tailscale (HTTPS) with a tailnet exposure note. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 91a65d4f89
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| fs.unlinkSync(legacyFile); | ||
| } catch { | ||
| // already gone |
There was a problem hiding this comment.
Keep legacy revocation durable when token-file deletion fails
If deleting web-token fails for any reason other than absence, such as an ACL or transient Windows file-lock error, this catch still reports a successful revocation after removing the row from web-access.json. On the next launch, createWebDevices() reads the surviving plaintext file and adopts the same credential as a new Legacy device, silently restoring the revoked access; ignore only ENOENT and surface or otherwise persist failures that leave the token file present.
Useful? React with 👍 / 👎.
| if (!enabled) { | ||
| // Do not leave a tailnet URL proxying to a closed port. | ||
| try { | ||
| if (server && (await tailscaleStatus()).serving) await setTailscale(false); | ||
| } catch (err) { | ||
| log(`solenta-web: tailscale serve off failed: ${errorText(err)}`); | ||
| } | ||
| await stop(); |
There was a problem hiding this comment.
Base last-window lifetime on the live Web server
When the app was launched with --serve-web, this newly reachable desktop action can stop the listener, but main.js still uses the unchanged serveOpts.enabled flag in both shouldBlockWindowClose and window-all-closed. After turning Solenta Web off and closing the final Windows/Linux window, the latter therefore returns and leaves an inaccessible headless Electron process running; the close policy needs to follow webAccess.isRunning() or keep the flag synchronized.
Useful? React with 👍 / 👎.
| } else if (!server || host !== (lan ? ANY : LOOPBACK)) { | ||
| try { | ||
| await start({ host: lan ? ANY : LOOPBACK, port: server ? server.port : undefined }); | ||
| } catch (err) { |
There was a problem hiding this comment.
Preserve the working listener when a LAN rebind fails
When a running loopback server is switched to LAN mode, start() first closes the existing listener and then attempts the wildcard bind. If another process occupies this port on a different interface, the wildcard bind fails even though the old loopback bind worked; this path throws after leaving server null, while the renderer retains its previous running: true status. Restart the previous binding on failure or avoid dropping it until the replacement listener succeeds.
Useful? React with 👍 / 👎.
Item I2 of #1512, also #245. It lives in Settings › Connections › Solenta Web.
What changed
<userData>/web-access.jsonat mode 0600.--serve-webprints it and Remote Connections reads it over SSH); revoking Legacy deletes it.qrcode-generator@2.0.4, lazy-loaded with no network call. It prefers the Tailscale, then LAN, then localhost URL.tailscaleis installed and logged in. It exposes over HTTPS with Stop, warns that this opens it to the whole tailnet, won't overwrite an existing HTTPS serve, and stops when Solenta Web turns off. It never installs Tailscale.Security (each item tested against a real local server):
authorize()hashes the presented token, thentimingSafeEquals it against every stored hash with no early exit.web:*handlers are desktop-only. I checked the bridge: it calls handlers with{ ...ctx, transport: "web" }, so a paired browser can't mint, revoke or toggle.Known limits
Verified independently, on main merged with this branch:
vite buildpasses.webDevices.jsand thewebBridge.jslimiter anddisconnect.🤖 Generated with Claude Code